/*
 * Copyright 2007-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose.web.impl.thread;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.paoding.rose.Rc;
import net.paoding.rose.RoseConstants;
import net.paoding.rose.web.annotation.AsSuperController;
import net.paoding.rose.web.annotation.Ignored;
import net.paoding.rose.web.annotation.REST;
import net.paoding.rose.web.annotation.ReqMapping;
import net.paoding.rose.web.impl.module.Module;
import net.paoding.rose.web.var.PrivateVar;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.ClassUtils;

/**
 * 
 * @author qieqie.wang@paoding.net
 * 
 */
public class ControllerInvocation implements WebInvocation {

	private static Log logger = LogFactory.getLog(ControllerInvocation.class);

	private Module module;
	private String controllerPath;
	private Object controller;
	private boolean proxiedController;
	private Class<?> controllerClass;
	private String viewPrefix;
	private ArrayList<ActionPathMapping> pathMappings = new ArrayList<ActionPathMapping>();
	private String controllerName;

	public ControllerInvocation(Module module, String controllerPath, Object controller,
			Class<?> controllerClass) {
		this.module = module;
		this.controllerPath = controllerPath;
		this.controller = controller;
		this.controllerClass = controllerClass;
		this.proxiedController = Proxy.isProxyClass(this.controller.getClass());
		if (logger.isDebugEnabled()) {
			logger.debug("it's a proxied controller: " + controllerClass.getName());
		}
		controllerName = ClassUtils.getShortNameAsProperty(controllerClass);
		this.viewPrefix = controllerName.substring(0, controllerName.length()
				- RoseConstants.CONTROLLER_SUFFIX.length())
				+ "-";
		init();
	}

	public String getControllerName() {
		return controllerName;
	}

	public Module getModule() {
		return module;
	}

	public String getViewPrefix() {
		return viewPrefix;
	}

	public String getControllerPath() {
		return controllerPath;
	}

	public Object getController() {
		return controller;
	}

	public Class<?> getControllerClass() {
		return controllerClass;
	}

	public boolean isProxiedController() {
		return proxiedController;
	}

	protected void init() {
		Class<?> clz = controllerClass;// 从clz得到的method不是aop后controller的clz阿!!!
		List<Method> pastMethods = new LinkedList<Method>();
		while (true) {
			Method[] declaredMethods = clz.getDeclaredMethods();
			for (Method method : declaredMethods) {
				if (!Modifier.isPublic(method.getModifiers())) {
					continue;
				}
				// 去除已经标志不作为接口的方法
				if (method.getAnnotation(Ignored.class) != null) {
					continue;
				}
				// 刚才在继承类(子类)已经声明的方法，不必重复处理了
				boolean broken = false;
				for (Method past : pastMethods) {
					if (past.getName().equals(method.getName())) {
						if (Arrays.equals(past.getParameterTypes(), method.getParameterTypes())) {
							broken = true;
							break;
						}
					}
				}
				if (broken) {
					continue;
				}

				String methodPath = "/" + method.getName();
				ActionInvocation actionInvocation = new ActionInvocation(this, method);
				// 解析@ReqMapping
				ReqMapping reqMappingAnnotation = method.getAnnotation(ReqMapping.class);
				String[] mappingPaths;
				if (reqMappingAnnotation != null) {
					mappingPaths = reqMappingAnnotation.path();
					// 如果mappingPaths.length==0，表示没有任何path可以映射到这个action了
					for (int i = 0; i < mappingPaths.length; i++) {
						if (ReqMapping.DEFAULT_PATH.equals(mappingPaths[i])) {
							mappingPaths[i] = methodPath;
						} else if (mappingPaths[i].length() > 0 && mappingPaths[i].charAt(0) != '/') {
							mappingPaths[i] = '/' + mappingPaths[i];
						} else if (mappingPaths[i].equals("/")) {
							mappingPaths[i] = "";
						}
					}
				} else {
					mappingPaths = new String[] { methodPath };
				}
				if (mappingPaths.length > 0) {
					for (int i = 0; i < mappingPaths.length; i++) {
						String mapping = mappingPaths[i];
						ActionPathMapping pathMapping = new ActionPathMapping(mapping, actionInvocation);
						pathMappings.add(pathMapping);
					}
				}
			}
			for (int i = 0; i < declaredMethods.length; i++) {
				pastMethods.add(declaredMethods[i]);
			}
			clz = clz.getSuperclass();
			if (clz == null || clz.getAnnotation(AsSuperController.class) == null) {
				break;
			}
		}
		Collections.sort(pathMappings);
		ActionPathMapping last = pathMappings.size() > 0 ? pathMappings.get(0) : null;
		ActionPathMapping showId = new ActionPathMapping("/${id:[0-9]+}", null);
		boolean showIdExists = false;
		for (int i = 1; last != null && i < pathMappings.size(); i++) {
			ActionPathMapping cur = pathMappings.get(i);
			if (cur.compareTo(last) == 0) {
				logger.error("控制器" + controller.getClass().getName() + "的映射" + cur.getMapping()
						+ "和已有的" + last.getMapping() + "映射为冲突配置，它将被忽略。");
				pathMappings.remove(i);
				i --; // 
			} else {
				last = cur;
				if (logger.isDebugEnabled()) {
					logger.debug("mapping: " + module.getPath() + controllerPath + cur);
				}
				if (!showIdExists && showId.isPathReplicated(cur)) {
					showIdExists = true;
				}
			}
		}
		// /user/123456自动映射到UserController的show()方法
		if (!showIdExists) {
			for (int i = 1; i < pathMappings.size(); i++) {
				ActionPathMapping pathMapping = pathMappings.get(i);
				String methodName = pathMapping.getInvocation().getMethod().getName(); 
				if ("show".equals(methodName)) {
					pathMappings.add(new ActionPathMapping("/${id:[0-9]+}", pathMapping.getInvocation()));
					break;
				}
			}
		}
	}

	@Override
	public Object invoke(HttpServletRequest request, HttpServletResponse response) throws Throwable {
		Path path = PrivateVar.invocationVariables().getPath();
		ActionInvocation actionInvocation = null;
		String[] candidates = new String[] { path.getAction() };
		if (candidates[0].length() == 0) {
			REST rest = controllerClass.getAnnotation(REST.class);
			String httpMethod = request.getMethod();
			if (rest == null) {
				if (httpMethod.equals("GET")) {
					candidates = new String[] { "/index", "/get", "/render" };
				} else if (httpMethod.equals("POST")) {
					candidates = new String[] { "/update", "/post", };
				} else {
					candidates = new String[] { "/" + request.getMethod().toLowerCase() };
				}
			} else {
				candidates = (String[]) REST.class.getMethod(httpMethod.toLowerCase()).invoke(rest);
				for (int i = 0; i < candidates.length; i++) {
					if (candidates[i].length() > 0 && candidates[i].charAt(0) != '/') {
						candidates[i] = '/' + candidates[i];
					}
				}
			}
		}
		MatchResult matchResult = null;
		for (String actionPath : candidates) {
			for (ActionPathMapping pathMapping : pathMappings) {
				matchResult = pathMapping.match(request, actionPath);
				if (matchResult != null) {
					actionInvocation = pathMapping.getInvocation();
					PrivateVar.invocationVariables().setActionMatchResult(matchResult);
					break;
				}
			}
			if (actionInvocation != null) {
				break;
			}
		}
		if (actionInvocation == null) {
			if (module.getPath().length() == 0) {
				return RoseConstants.CONTINUE;
			} else {
				return "error:404;[" + controllerClass.getSimpleName()
						+ "] not found action method for path " + Arrays.toString(candidates)
						+ " when " + request.getMethod() + " " + path.getUri();
			}
		}
		// 到此确认，这个请求应由Rose来处理
		//
		PrivateVar.invocationVariables().setActionInvocation(actionInvocation);
		Rc.model().add("this", controller);
		for (String matchResultParam : matchResult.getParameterNames()) {
			Rc.model().add(matchResultParam, matchResult.getParameter(matchResultParam));
		}
		return actionInvocation.invoke(request, response);
	}
	
	@Override
	public String toString() {
		return getControllerClass().getName();
	}

}
